#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "dragframe.h"

#include <QModbusRtuSerialMaster>
#include <QNetworkInterface>

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    iniFile = QCoreApplication::applicationDirPath() + "/config.ini";
    // 设置界面风格
    QStringList keyList = QStyleFactory::keys();
    QActionGroup* appStyleGroup = new QActionGroup(this);
    QSettings configIni(iniFile, QSettings::IniFormat);
    QString styleName = configIni.value("sys/style").toString();

    for(int i = 0; i < keyList.length(); i++) // 当前系统支持的风格列表
    {
        QAction* appStyle = new QAction(this);  // 为每个风格建立一个菜单
        appStyle->setText(keyList.at(i));
        appStyle->setCheckable(true);
        connect(appStyle, &QAction::triggered, this, [ = ]()   // 风格菜单槽函数
        {
            QApplication::setStyle(QStyleFactory::create(appStyle->text()));
            QSettings configIni(iniFile, QSettings::IniFormat);
            configIni.setValue("sys/style", appStyle->text());
        });      //关联槽函数
        ui->menuAppStyle->addAction(appStyle);
        appStyleGroup->addAction(appStyle);

        if(styleName == keyList.at(i))
        {
            appStyle->setChecked(true);
            QApplication::setStyle(QStyleFactory::create(styleName));
        }
    }

    // 受权检查
    QSettings ini("/home/pi/.config/network000", QSettings::IniFormat);
    QString MAC = ini.value("setting/mac", "").toString().toUpper();
    authorized = false;
    QList<QNetworkInterface> networkInterface = QNetworkInterface::allInterfaces();

    for(QList<QNetworkInterface>::const_iterator it = networkInterface.constBegin();
        it != networkInterface.constEnd(); ++it)
    {
        if(((*it).humanReadableName() == "eth0") and ((*it).hardwareAddress().toUpper() == MAC))
        {
            authorized = true;  // 已受权
        }

        //qDebug() << (*it).hardwareAddress().toUpper() << authorized << MAC;
    }

    if(!dbConnection())
    {
        QMessageBox::critical(this, tr("数据库创建失败"), tr("数据库创建或打开失败！"),
                              QMessageBox::Ok);
        return;
    }

    readCardTimer = new QTimer(this);
#ifndef _DEBUG_MODE_
    ui->actiontest->setVisible(false);

    if(authorized)  // 受权
#endif
    {
        connect(readCardTimer, &QTimer::timeout, this, &MainWindow::readCardValue);
    }

    initFrom();
    initThingsBoard();
#ifndef _DEBUG_MODE_
    this->showFullScreen();  // 全屏显示
    on_actionConnect_triggered();
#endif
}

MainWindow::~MainWindow()
{
    on_actionDisconnect_triggered();
    QSettings configIni(iniFile, QSettings::IniFormat);
    configIni.setValue("sys/Volume", sysVolume);
    configIni.setValue("sys/AlarmVolume", alarmVolume);
    db.close();
    delete ui;
}

// 建立数据库连接，创建数据表
bool MainWindow::dbConnection()
{
    db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName("msa.db");

    if(!db.open())
    {
        return false;
    }

    QSqlQuery query;
    // 创建浓度表
    bool r1 = query.exec("CREATE TABLE IF NOT EXISTS concentration("
                         "    id integer primary key AUTOINCREMENT NOT NULL,"
                         "    chname varchar(100) NOT NULL,"
                         "    numtime timestamp NOT NULL,"
                         "    value float NOT NULL,"
                         "    unit varchar(10) NOT NULL)");
    // 创建报警记录表
    bool r2 = query.exec("CREATE TABLE IF NOT EXISTS alarm("
                         "    id integer primary key AUTOINCREMENT NOT NULL,"
                         "    chname varchar(100) NOT NULL,"
                         "    numtime timestamp NOT NULL,"
                         "    numvalue varchar(250))");
    return r1 && r2;
}

// 初始化9020卡MODBUS地址、AB通道名，并建立分配了MODBUS地址的卡对列
void MainWindow::initMsaCard()
{
    QSettings configIni(iniFile, QSettings::IniFormat);
    ws.clear();

    for(int i = 0; i < MAX_CARD_COUNT; i++)
    {
        configIni.beginGroup("msa");
        int addr = configIni.value(QString("addr%1").arg(i + 1),
                                   0).toInt();
        QString chaname = configIni.value(QString("a%1").arg(i + 1),
                                          tr("未使用")).toString();
        QString chbname = configIni.value(QString("b%1").arg(i + 1),
                                          tr("未使用")).toString();
        configIni.endGroup();
        w[i]->setModbusAddr(addr);
        w[i]->setChabName(chaname, chbname);

        if(addr > 0)
        {
            ws.insert(addr, w[i]); // modbus地址跟DragFrame关联，通过modbus地址访问DragFrame
        }
    }

    httpHost = configIni.value("net/hostname", "127.0.0.1").toString();
    httpUserName = configIni.value("net/username", "").toString();
    httpPort = configIni.value("net/port", 9090).toInt();
    updateTB = configIni.value("net/tb", 0).toBool();
    historyRecodeSaveDays = configIni.value("sys/day", 30).toInt();
}

// 绘制程序主界面、状态栏
void MainWindow::initFrom()
{
    QSettings configIni(iniFile, QSettings::IniFormat);
    // 绘制程序主界面
    setCentralWidget(ui->scrollArea);
    QGridLayout* grid = new QGridLayout();
    int x = MAX_COLS, y = MAX_ROWS;

    for(int i = 0; i < y; i++)
        for(int j = 0; j < x; j++)
        {
            w[i * x + j] = new DragFrame(this, i * x + j);
            connect(w[i * x + j], &DragFrame::playWarningSound, this,
                    &MainWindow::playWarningSound);
            w[i * x + j]->show();
            grid->addWidget(w[i * x + j], i, j, 1, 1);
        }

    ui->scrollArea->setLayout(grid);
    // 初始9020卡
    initMsaCard();
    // 状态栏：通讯
    ComStatic = new QLabel("通讯端口状态", this);
    ComStatic->setMinimumWidth(135);
    ComStatic->setAlignment(Qt::AlignCenter);  //水平居中(HCenter)。
    statusBar()->addPermanentWidget(
    ComStatic); //现实永久信息
    statusBar()->showMessage(this->windowTitle());
    ledTx = new QLabel("<font>&#9660;</font>", this);
    ledTx->setAlignment(Qt::AlignCenter);  //水平居中(HCenter)。
    ledTx->setStyleSheet("color:gray;");//改成 灰色圆形
    statusBar()->addPermanentWidget(ledTx);
    ledTxOff = new QTimer(
    this);                                        //创建定时器
    connect(ledTxOff, &QTimer::timeout, this, [ = ]()
    {
        ledTx->setStyleSheet("color:gray;");
        ledTxOff->stop();
    });
    // ledLime = new ledThread(ledTx);
    ledRx = new QLabel("<font>&#9650;</font>", this);
    ledRx->setAlignment(Qt::AlignCenter);  //水平居中(HCenter)。
    ledRx->setStyleSheet("color:gray;");//改成 灰色圆形
    statusBar()->addPermanentWidget(ledRx);
    ledRxOff = new QTimer(this);   //创建定时器
    connect(ledRxOff, &QTimer::timeout, this, [ = ]()
    {
        ledRx->setStyleSheet("color:gray;");
        ledRxOff->stop();
    });
    // ledRed = new ledThread(ledRx);
    // 状态栏：时钟
    QLabel* ClockStatic = new QLabel("", this);
    ClockStatic->setMinimumWidth(160);
    ClockStatic->setAlignment(Qt::AlignCenter);  //水平居中(HCenter)。
    statusBar()->addPermanentWidget(ClockStatic); //现实永久信息
    QTimer* clocktime = new QTimer(this);
    connect(clocktime, &QTimer::timeout, [ = ]()
    {
        QDateTime t = QDateTime::currentDateTime();
        ClockStatic->setText(t.toString("yyyy-MM-dd hh:mm:ss"));
    });
    clocktime->start(1000);
    // 状态栏：版权
    QLabel* Copyright = new
    QLabel("<font>Copyright&#169; 2019-2022</font> 武汉中祺科技", this);
    Copyright->setMinimumWidth(270);
    Copyright->setAlignment(Qt::AlignCenter);  //水平居中(HCenter)。
    statusBar()->addPermanentWidget(Copyright); //现实永久信息
    // 报警音
    player = new QMediaPlayer;
    playlist = new QMediaPlaylist(player);
    playlist->setPlaybackMode(QMediaPlaylist::CurrentItemOnce); // 播放1次
    dir = new QDir("/home/pi/Music");
    QStringList filter;
    filter << QString("*.mp3");
    fileInfo = new QList<QFileInfo>(dir->entryInfoList(filter));
    dlgvol = new DialogVol;
    dlgvol->setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
    connect(dlgvol, &DialogVol::setAlarmVol, [ = ](int v)
    {
        alarmVolume = v;
    });
    connect(dlgvol, &DialogVol::setSysVol, [ = ](int v)
    {
        player->setVolume(v);
        sysVolume = v;
    });
    playlist->clear();

    for(int i = 0; i < fileInfo->count(); i++)
    {
        playlist->addMedia(QUrl::fromLocalFile(fileInfo->at(i).filePath()));
    }

    player->setPlaylist(playlist);
    sysVolume = configIni.value("sys/Volume", 50).toInt();
    alarmVolume = configIni.value("sys/AlarmVolume", 20).toInt();
    alarmFileIndex = configIni.value("sys/alarmSoundIndex", 1).toInt();
    dlgvol->initVol(sysVolume, alarmVolume);
    QActionGroup* alarmGroup = new QActionGroup(this);
    alarmGroup->addAction(ui->actionmusic1);
    alarmGroup->addAction(ui->actionmusic2);
    alarmGroup->addAction(ui->actionmusic3);
    alarmGroup->addAction(ui->actionmusic4);
    alarmGroup->addAction(ui->actionmusic5);
    alarmGroup->addAction(ui->actionmusic6);
    alarmGroup->addAction(ui->actionmusic7);
    alarmGroup->actions().at(alarmFileIndex - 1)->setChecked(true);
    connect(ui->actionmusic1, &QAction::triggered, this, [ = ]()
    {
        playAlarmSound(1);
        QSettings ini(iniFile, QSettings::IniFormat);
        ini.setValue("sys/alarmSoundIndex", 1);
        alarmFileIndex = 1;
    });
    connect(ui->actionmusic2, &QAction::triggered, this, [ = ]()
    {
        playAlarmSound(2);
        QSettings ini(iniFile, QSettings::IniFormat);
        ini.setValue("sys/alarmSoundIndex", 2);
        alarmFileIndex = 2;
    });
    connect(ui->actionmusic3, &QAction::triggered, this, [ = ]()
    {
        playAlarmSound(3);
        QSettings ini(iniFile, QSettings::IniFormat);
        ini.setValue("sys/alarmSoundIndex", 3);
        alarmFileIndex = 3;
    });
    connect(ui->actionmusic4, &QAction::triggered, this, [ = ]()
    {
        playAlarmSound(4);
        QSettings ini(iniFile, QSettings::IniFormat);
        ini.setValue("sys/alarmSoundIndex", 4);
        alarmFileIndex = 4;
    });
    connect(ui->actionmusic5, &QAction::triggered, this, [ = ]()
    {
        playAlarmSound(5);
        QSettings ini(iniFile, QSettings::IniFormat);
        ini.setValue("sys/alarmSoundIndex", 5);
        alarmFileIndex = 5;
    });
    connect(ui->actionmusic6, &QAction::triggered, this, [ = ]()
    {
        playAlarmSound(6);
        QSettings ini(iniFile, QSettings::IniFormat);
        ini.setValue("sys/alarmSoundIndex", 6);
        alarmFileIndex = 6;
    });
    connect(ui->actionmusic7, &QAction::triggered, this, [ = ]()
    {
        playAlarmSound(7);
        QSettings ini(iniFile, QSettings::IniFormat);
        ini.setValue("sys/alarmSoundIndex", 7);
        alarmFileIndex = 7;
    });
}

void MainWindow::serialDisConnection()
{
    serialPort->close();
    delete serialPort;
    serialPort = nullptr;
    ComStatic->setText(tr("串口关闭！"));
    ui->actionConnect->setEnabled(true);
    ui->actionslaveserial->setEnabled(true);
}

// 从机模式连接
bool MainWindow::serialConnection()
{
    QSettings configIni(iniFile, QSettings::IniFormat);
    configIni.beginGroup("com");
    QString portName = configIni.value("portname", "").toString();
    int baudRate = configIni.value("baudrate", 38400).toInt();
    configIni.endGroup();
    serialPort = new QSerialPort(this);
    connect(serialPort, &QSerialPort::errorOccurred, [ = ]()
    {
        statusBar()->showMessage(serialPort->errorString(), 5000);
    });
    serialPort->setPortName(portName);
    serialPort->setParity(QSerialPort::NoParity);
    serialPort->setBaudRate(static_cast<QSerialPort::BaudRate>(baudRate));
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setStopBits(QSerialPort::OneStop);
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::readResponse);

    if(serialPort->open(QIODevice::ReadOnly))
    {
        ComStatic->setText(tr("连接成功:%1").arg(portName));
        return true;
    }
    else
    {
        QMessageBox::critical(this, tr("打开失败"),
                              tr("未能打开串口 ") + portName +
                              tr("\n该串口设备不存在或已被占用"),
                              QMessageBox::Ok);
        return false;
    }
}

void MainWindow::readResponse()
{
#pragma pack(push,1)
    typedef union tempData
    {
        struct
        {
            quint8 slaveAddr;
            quint8 funcCode;
            quint8 numSize;
            quint16 num[24];
            quint16 crc;
        } c;
        char data[53];
    } TEMP_DATA;
#pragma pack(pop)
    QByteArray resData = serialPort->readAll();

    while(serialPort->waitForReadyRead(30))
    {
        resData += serialPort->readAll();
        QCoreApplication::processEvents();
    }

    qDebug() << "Read: " << resData.toHex(' ');

    if(resData.size() == 53)
    {
        TEMP_DATA td;

        for(int i = 0; i < 53; i++)
        {
            td.data[i] = resData[i];
        }

        QVector<quint16> ui16;
        ui16.resize(24);

        for(int i = 0; i < 24; i++)
        {
            quint16 t = td.c.num[i];
            ui16[i] = quint16((t & 0xff) << 8) | (t >> 8);
        }

        ws[td.c.slaveAddr]->setValues(ui16);
    }
}


// 设置串口通讯参数，开始轮询
bool MainWindow::modbusConnection()
{
    if(modbusDevice)
    {
        modbusDevice->disconnectDevice();
        delete modbusDevice;
        modbusDevice = nullptr;
    }

    QSettings configIni(iniFile, QSettings::IniFormat);
    configIni.beginGroup("com");
    QString portName = configIni.value("portname", "").toString();
    int baudRate = configIni.value("baudrate", 38400).toInt();
    int delyTime = configIni.value("delytime", 50).toInt();
    configIni.endGroup();
    modbusDevice = new QModbusRtuSerialMaster(this);
    connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error)
    {
        statusBar()->showMessage(modbusDevice->errorString(), 5000);
    });
    connect(modbusDevice, &QModbusClient::stateChanged, this, &MainWindow::onModbusStateChanged);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, portName);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
                                         static_cast<QSerialPort::BaudRate>(baudRate));
    modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
    modbusDevice->setTimeout(1000); // 超时时间
    modbusDevice->setNumberOfRetries(0); // 重连次数

    if(!modbusDevice->connectDevice())
    {
        ComStatic->setText(tr("连接失败: ") + modbusDevice->errorString());
        qDebug() << modbusDevice->errorString();
        return false;
    }
    else
    {
        readCardValue();
        // 开始数据查询轮询
        readCardTimer->start(delyTime * 1000);
        return true;
    }
}

void MainWindow::onModbusStateChanged(int state)
{
    bool connected = (state != QModbusDevice::UnconnectedState);
    ui->actionConnect->setEnabled(!connected);
    ui->actionslaveserial->setEnabled(!connected);
    ui->actionDisconnect->setEnabled(connected);

    if(state == QModbusDevice::UnconnectedState)
    {
        ComStatic->setText(tr("连接关闭."));
    }
    else if(state == QModbusDevice::ConnectedState)
    {
        QString cn = modbusDevice->connectionParameter(QModbusDevice::SerialPortNameParameter).toString();
        ComStatic->setText(tr("连接成功: ") + cn);
    }
}

void MainWindow::setLedRxOn()
{
    ledRx->setStyleSheet("color:rgb(0,255,0);");

    if(!ledRxOff->isActive())
    {
        ledRxOff->start(100);
    }
}

void MainWindow::setLedTxOn()
{
    ledTx->setStyleSheet("color:rgb(255,0,0);");

    if(!ledTxOff->isActive())
    {
        ledTxOff->start(200);
    }
}

// ![0] 读MSA卡的单位，A通道读一次
void MainWindow::readMsaUnitA(int cardIndex)
{
    if(!modbusDevice)
    {
        return;
    }

    int slaveAddr = w[cardIndex]->getModbusAddr();

    if(slaveAddr == 0)
    {
        return;
    }

    setLedTxOn();
    // A通道测量单位地址:13;
    QModbusDataUnit ss = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, UNIT_MEASUR_CHA, 1);

    if(auto* replyA = modbusDevice->sendReadRequest(ss, slaveAddr))
    {
        if(!replyA->isFinished())
        {
            if(replyA->error() == QModbusDevice::NoError)
            {
                connect(replyA, &QModbusReply::finished, this, &MainWindow::readReadyUnitA);
            }
        }
        else
        {
            delete replyA;    // broadcast replies return immediately
        }
    }
    else
    {
        statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
    }
}
// A通道槽函数
void MainWindow::readReadyUnitA()
{
    auto reply = qobject_cast<QModbusReply*>(sender());

    if(!reply)
    {
        return;
    }

    if(reply->error() == QModbusDevice::NoError)
    {
        setLedRxOn();
        const QModbusDataUnit unit = reply->result();
        const QString entry = QString("Modbus address: %1, A Unit value: %2").arg(
                              reply->serverAddress()).arg(
                              QString::number(unit.value(0)));
        ws[reply->serverAddress()]->setUnitA(unit.value(0));  // 保存浓度单位到Dragframe
        qDebug() << QString("Unit(A%1): ").arg(reply->serverAddress()) <<
                 reply->rawResult().data().toHex(' ');
    }
    else if(reply->error() == QModbusDevice::TimeoutError)
    {
        statusBar()->showMessage(tr("CHA-%3: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(),
                                 -1, 16).arg(reply->serverAddress()), 5000);
    }

    reply->deleteLater();
}
// ![0]

// ![1] 读MSA卡的单位，B通道读一次
void MainWindow::readMsaUnitB(int cardIndex)
{
    if(!modbusDevice)
    {
        return;
    }

    int slaveAddr = w[cardIndex]->getModbusAddr();

    if(slaveAddr == 0)
    {
        return;
    }

    setLedTxOn();

    // B通道测量单位地址:57
    if(auto* replyB = modbusDevice->sendReadRequest(QModbusDataUnit(QModbusDataUnit::HoldingRegisters,
                                                    UNIT_MEASUR_CHB, 1), slaveAddr))
    {
        if(!replyB->isFinished())
        {
            if(replyB->error() == QModbusDevice::NoError)
            {
                connect(replyB, &QModbusReply::finished, this, &MainWindow::readReadyUnitB);
            }
        }
        else
        {
            delete replyB;    // broadcast replies return immediately
        }
    }
    else
    {
        statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
    }
}
// 槽函数
void MainWindow::readReadyUnitB()
{
    auto reply = qobject_cast<QModbusReply*>(sender());

    if(!reply)
    {
        return;
    }

    if(reply->error() == QModbusDevice::NoError)
    {
        setLedRxOn();
        const QModbusDataUnit unit = reply->result();
        const QString entry = QString("Modbus address: %1, B Unit value: %2").arg(
                              reply->serverAddress()).arg(
                              QString::number(unit.value(0)));
        ws[reply->serverAddress()]->setUnitB(unit.value(0)); // 保存浓度单位到Dragframe
        qDebug() << tr("Unit(B%1): ").arg(reply->serverAddress()) << reply->rawResult().data().toHex(' ');
    }
    else if(reply->error() == QModbusDevice::TimeoutError)
    {
        statusBar()->showMessage(tr("CHB-%3: %1 (code: 0x%2)").
                                 arg(reply->errorString()).
                                 arg(reply->error(), -1, 16).
                                 arg(reply->serverAddress()),
                                 5000);
    }

    reply->deleteLater();
}
// ![1]

// 定时读取每张卡的浓度数据
void MainWindow::readCardValue()
{
    for(int i = 0; i < MAX_CARD_COUNT; i++)
    {
        readMsaData(i);
        QCoreApplication::processEvents();
    }
}

// ![2] 读MSA卡的浓度
void MainWindow::readMsaData(int cardIndex)
{
    if(!modbusDevice)
    {
        return;
    }

    // 如果卡通道没有浓度单位，就获取1次
    if(w[cardIndex]->getUnitA() == "")
    {
        readMsaUnitA(cardIndex);
    }

    if(w[cardIndex]->getUnitB() == "")
    {
        readMsaUnitB(cardIndex);
    }

    int slaveAddr = w[cardIndex]->getModbusAddr();

    if(slaveAddr > 0)
    {
        setLedTxOn();

        if(auto* reply = modbusDevice->sendReadRequest(
                         QModbusDataUnit(QModbusDataUnit::HoldingRegisters,
                                         CARD_INFO_ADDR, 24), slaveAddr))
        {
            if(!reply->isFinished())
            {
                connect(reply, &QModbusReply::finished, this,
                        &MainWindow::readReady);
            }
            else
            {
                delete reply;    // broadcast replies return immediately
            }
        }
        else
        {
            statusBar()->showMessage(tr("Read error: ") +
                                     modbusDevice->errorString(), 5000);
        }
    }
}
// 槽函数
void MainWindow::readReady()
{
    auto reply = qobject_cast<QModbusReply*>(sender());

    if(!reply)
    {
        return;
    }

    if(reply->error() == QModbusDevice::NoError)
    {
        setLedRxOn();
        const QModbusDataUnit unit = reply->result();
        ws[reply->serverAddress()]->setValues(unit.values());
        qDebug() << tr("Data(%1): ").arg(reply->serverAddress()) <<
                 reply->rawResult().data().toHex(' ');
    }
    else if(reply->error() == QModbusDevice::TimeoutError)
    {
        ws[reply->serverAddress()]->dispTimeout();
    }
    else
    {
        statusBar()->showMessage(
        tr("Read response error: %1 (code: 0x%2)").arg(
        reply->errorString()).arg(
        reply->error(), -1, 16), 5000);
    }

    reply->deleteLater();
}
// ![2]

// 退出程序
void MainWindow::on_actionexit_triggered()
{
    close();
}

// 连接MODBUS主机
void MainWindow::on_actionConnect_triggered()
{
    bool r = modbusConnection();
    ui->actionConnect->setEnabled(!r);
    ui->actionDisconnect->setEnabled(r);
}

// 关闭MODBUS连接
void MainWindow::on_actionDisconnect_triggered()
{
    readCardTimer->stop();

    if(modbusDevice)
    {
        modbusDevice->disconnectDevice();
        delete modbusDevice;
        modbusDevice = nullptr;
    }

    ui->actionConnect->setEnabled(true);
    ui->actionDisconnect->setEnabled(false);
}

// 显示历史记录
void MainWindow::on_actionhistory_triggered()
{
    if(historyWidget != nullptr)
    {
        delete historyWidget;
        historyWidget = nullptr;
    }

    historyWidget = new History();
    historyWidget->setWindowModality(Qt::ApplicationModal);
    deleteRecoder();  // 删除历史记录
#ifndef _DEBUG_MODE_
    historyWidget->showFullScreen();  // 全屏显示
#else
    historyWidget->show();
#endif
}

// 显示设置窗口
void MainWindow::on_actionOption_triggered()
{
    if(opt == nullptr)
    {
        opt = new options;
    }

    opt->setWindowModality(Qt::ApplicationModal); //顶端窗口
    connect(opt, &options::paramChanged, this, [ = ]()
    {
        initMsaCard();
    });
    connect(opt, &options::testWarningSound, this, &MainWindow::testWarningSound);
    opt->add_mp3(fileInfo);
    opt->show();
}

// 重启电脑
void MainWindow::on_actionreboot_triggered()
{
    int ret = QMessageBox::question(this, tr("确认"),
                                    tr("请问是否重启动服务器？"),
                                    QMessageBox::Yes | QMessageBox::No);

    if(ret == QMessageBox::Yes)
    {
#ifdef Q_OS_LINUX
        system("/sbin/reboot");
#endif
#ifdef Q_OS_WIN
        system("shutdown /r");
#endif
    }
}

// 关闭电脑
void MainWindow::on_actionshutdown_triggered()
{
    int ret = QMessageBox::question(this, tr("确认"),
                                    tr("请问是否关闭服务器？"),
                                    QMessageBox::Yes | QMessageBox::No);

    if(ret == QMessageBox::Yes)
    {
#ifdef Q_OS_LINUX
        system("/sbin/shutdown -h now");
#endif
#ifdef Q_OS_WIN
        system("shutdown /p /f");
#endif
    }
}

// 播放警告音
void MainWindow::playWarningSound()
{
    if(player->state() == QMediaPlayer::PlayingState)
    {
        return;
    }

    QSettings configIni(iniFile, QSettings::IniFormat);
    bool sound_random = configIni.value("sys/mp3_random", false).toBool();
    int sound_index = configIni.value("sys/mp3", 0).toInt();

    if(sound_random)
    {
        //随机播放
        playlist->setPlaybackMode(QMediaPlaylist::Random);
    }
    else
    {
        playlist->setCurrentIndex(sound_index);
        //playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);  // 单曲循环播放
        playlist->setPlaybackMode(QMediaPlaylist::CurrentItemOnce);  // 单曲播放一次
    }

    player->setVolume(sysVolume);
    player->play();
    playAlarmSound(alarmFileIndex);  // 打开声光报警器
}

void MainWindow::testWarningSound(int index)
{
    playlist->setCurrentIndex(index);
    playlist->setPlaybackMode(QMediaPlaylist::CurrentItemOnce);
    player->setVolume(sysVolume);
    player->play();
}

// 显示音量调节窗口
void MainWindow::on_actionvolume_triggered()
{
    dlgvol->move(cursor().pos());
    dlgvol->show();
}


// 屏幕键盘
void MainWindow::on_actiononbord_triggered()
{
#ifdef Q_OS_LINUX
    QProcess::startDetached("python3 /usr/bin/onboard");
#endif
}

// 从机模式
void MainWindow::on_actionslaveserial_triggered()
{
    bool r = serialConnection();
    ui->actionConnect->setEnabled(!r);  // 连接主机
    ui->actionslaveserial->setEnabled(!r); // 从机模式
    ui->actioncloseSlave->setEnabled(r); // 退出从机模式
}

// 退出从机模式
void MainWindow::on_actioncloseSlave_triggered()
{
    serialDisConnection();
    ui->actionConnect->setEnabled(true);  // 连接主机
    ui->actionslaveserial->setEnabled(true); // 从机模式
    ui->actioncloseSlave->setEnabled(false); // 退出从机模式
}

// 显示关于窗口
void MainWindow::on_actionabout_triggered()
{
    AboutDialog* aboutdlg = new AboutDialog;
    aboutdlg->setWindowModality(Qt::ApplicationModal); //顶端窗口
    aboutdlg->setAttribute(Qt::WA_DeleteOnClose);
    aboutdlg->setAuthorized(authorized);
    aboutdlg->show();
    // 在主窗口中间显示
    int x = this->x() + (this->width() - aboutdlg->width()) / 2;
    int y = this->y() + (this->height() - aboutdlg->height()) / 2;
    aboutdlg->move(x, y);
}

// 测试报警音
void MainWindow::on_actiontestalarmsound_triggered()
{
    playWarningSound();
}

// 复位9020卡
void MainWindow::on_actionreset_triggered()
{
    bool ok;

    if(!modbusDevice)
    {
        statusBar()->showMessage(tr("Modbus客户端未连接。"),
                                 3000);
        return;
    }

#if 1
    int modbusaddr = QInputDialog::getInt(this,
                                          tr("卡槽复位"), tr("请输入卡槽Modbus地址:"), 1,
                                          0, 254, 1, &ok);
#else
    QInputDialog* i = new QInputDialog(this);
    int modbusaddr = i->getInt(this, tr("卡槽复位"), tr("请输入卡槽Modbus地址:"), 1, 0, 254,
                               1, &ok);
#endif

    if(ok)
    {
        setLedTxOn();
        //        modbusDevice->sendRawRequest(QModbusRequest(QModbusPdu::MsaPermits, QByteArray::fromHex("")),
        //                                     modbusaddr); // qmodbuspdu.h 73行加入MsaPermits = 0x09
        modbusDevice->sendRawRequest(QModbusRequest(static_cast<QModbusPdu::FunctionCode>(0x09),
                                                    QByteArray::fromHex("")),
                                     modbusaddr); // qmodbuspdu.h 73行加入MsaPermits = 0x09,
        QString s = QString("  地址%1的卡槽已复位.").arg(modbusaddr);
        statusBar()->showMessage(s, 3000);
    }
}


void MainWindow::initThingsBoard()
{
    //QString topic = "v1/devices/me/attributes/msa9020";
    QString topic = "v1/devices/me/telemetry";

    for(int i = 0; i < MAX_CARD_COUNT; i++)
    {
        connect(w[i], &DragFrame::sendMqtt, this, &MainWindow::httpPost);
    }
}
// 向Thingsboard服务器提交数据
void MainWindow::httpPost(int id, QString json)
{
    if(!updateTB)
    {
        return;
    }

    if(m_http == nullptr)
    {
        m_http = new QNetworkAccessManager;
    }

    QString url = QString("http://%1:%2/api/v1/%3%4/telemetry").arg(httpHost).arg(
                  httpPort).arg(httpUserName).arg(id);
    QNetworkRequest requestInfo;
    requestInfo.setUrl(QUrl(url));
    requestInfo.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
    QByteArray data = json.toUtf8();
    QNetworkReply* reply =  m_http->post(requestInfo, data);
    connect(reply, &QNetworkReply::finished, this, [ = ]()
    {
        if(reply->error())
        {
            qDebug() << reply->errorString();
        }
    });
    //qDebug() << url;
}

// 删除历史记录
void MainWindow::deleteRecoder()
{
    QSqlQuery query;
    QDateTime dateTime = QDateTime::currentDateTime();
    uint  now = dateTime.toTime_t();//当前时间转化为秒
    uint ts = now - uint(historyRecodeSaveDays * 24 * 60 * 60);
    QString sqlcmd = QString("delete from concentration where numtime<=%1").arg(ts);
    query.exec(sqlcmd);
    sqlcmd = QString("delete from alarm where numtime<=%1").arg(ts);
    query.exec(sqlcmd);
}



void MainWindow::playAlarmSound(int fileIndex)
{
#pragma pack(push,1)
    typedef union tempData
    {
        struct
        {
            quint8 addr1;
            quint8 addr2;
            quint8 vol;         // 音量
            quint8 fileIndex;   // 声光报警器音频序号
        } c;
        char data[4];
    } TEMP_DATA;
#pragma pack(pop)

    if(!modbusDevice)
    {
        statusBar()->showMessage(tr("Modbus客户端未连接。"), 3000);
        return;
    }

    TEMP_DATA td;
    td.c.addr1 = 0x00;
    td.c.addr2 = 0x22;
    td.c.vol = quint8(0xff & alarmVolume);
    td.c.fileIndex = quint8(0xff & fileIndex);

    if(auto* reply = modbusDevice->sendRawRequest(QModbusRequest(QModbusPdu::WriteSingleRegister,
                                                  QByteArray(td.data, 4)), JR_SG01_MODBUS_ADDR))
    {
        //qDebug() << "声光报警器！";
        if(!reply->isFinished())
        {
            if(reply->error() == QModbusDevice::NoError)
            {
                connect(reply, &QModbusReply::finished, this, [ = ]()
                {
                    auto replyVol = qobject_cast<QModbusReply*>(sender());

                    if(!replyVol)
                    {
                        return;
                    }

                    if(replyVol->error() == QModbusDevice::NoError)
                    {
                        setLedRxOn();
                        const QModbusDataUnit unit = reply->result();
                        //dlgvol->initAlarmVol(unit.value(1));
                    }

                    replyVol->deleteLater();
                });
            }
        }
        else
        {
            delete reply;    // broadcast replies return immediately
        }
    }
    else
    {
        statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
    }
}

